<?php

/**
 * This class creates a processing object to process the json-rpc request
 *
 * @author Matthew P. C. Morley
 * @copyright Copyright (c) 2011, MPCM Technologies LLC
 */
class jsonrpc2 {

  //Store all the error codes defined by the specification
  private $errors = array(
    -32600  =>  'Invalid Request',
    -32601	=>  'Method not found',	
    -32602	=>  'Invalid params',	
    -32603	=>  'Internal error',
    -32700	=>  'Parse error',
  );
  
  //Define a containter for the methods we will allow to be called
	private $methods 	= array();
	
	/**
    * Take an array of method maps to serve up
    * @param array $methods
   */
	public function __construct( $methods ) {
	
    //if anything other than an array is passed, throw an expection
    if( !is_array( $methods ) ) {
      throw new Exception( 'Requires a method map that is an array.' );
    }
    
    //move the methods into the instance storage variable
    $this->methods = $methods;
  }

  /**
   * process the payload, either an array of requests, or a single request
   * @param complex $payload
   * @return complex the results from processing the payload
   */
  public function dispatch( $payload ){
    
    //if the payload is a single request object
    if( is_object( $payload ) ) {
      
      //process it directly and return the response object
      return $this->dispatch( $payload );
      
    }
    //if the payload is an array of requests
    elseif( is_array( $payload ) ) {
      
      //shuffle is in here to establish the point that batch requests
      //are NOT always processed in the order of the array.
      shuffle($payload);
    
      //create a storage point for the response objects
      $return = array();
    
      //for every item in the payload, dispatch the request
      foreach($payload as $request_obj) {

        //process the request
        $response_obj = $this->dispatch($request_obj);
    
        //if the result is an object
        if( is_object( $response_obj ) ){

          //store it in the return array
          $return[] = $a;

        }

      }

      //return the set of responses
      return $return;

    }
    else{

      //throw an exception
      throw new Exception( 'dispatch requires either a request object or an array of request objects' );

    }
  
  }
    
  private function dispatch_request($request){
    if(!isset($request->jsonrpc)) $request->jsonrpc = '1.0';
      if($request->jsonrpc == '2.0' && !isset($request->params)) $request->params = array();
      $response = $this->invoke($request);
      if(!isset($request->id) || is_null($request->id)) return;
  $response->id = $request->id;
      if($request->jsonrpc == '2.0') $response->jsonrpc = '2.0';
      else{
        if(!isset($response->result)) $response->result = null;
        if(!isset($response->error)) $response->error = null;
      }
      return $response;
  }
    
    private function invoke($request){
    	$error = -32601;
        try{
            if(!isset($this->methods[$request->method])) throw new Exception(-32601);
            $m = $this->methods[$request->method];
            if(!is_array($m)) $m = array($m);
            $r = isset($m[1]) ? new ReflectionMethod($m[1], $m[0]) : new ReflectionFunction($m[0]);
            $p = $this->checkParams($r->getParameters(), $request->params);
            if(!isset($m[1]))       return (object) array('result' =>$r->invokeArgs($p));
            if(is_object($m[1]))    return (object) array('result' =>$r->invokeArgs($m[1], $p));
            if(is_string($m[1]))    return (object) array('result' =>$r->invokeArgs(new $m[1], $p));
        }
        catch(Exception $e){
        	$error = $e->getMessage();
        }
        return (object) array('error'=> (object) array('code'=> $error, 'message'=>$this->errors[$error]));
    }
	
    private function checkParams($real, $sent){
        $new = array();
        $is_obj = is_object($sent);
        $is_assoc = array_keys($sent) != range(0, count($sent) - 1);
        foreach($real as $i=>$param)
        {
        	$name = $param->getName();
            if($is_obj &&  isset($sent->{$name})) $new[$i] = $sent->{$name};
            elseif($is_assoc && $sent[$name]) $new[$i] = $sent[$name];
            elseif( isset($sent[$i])) $new[$i] = $sent[$i];
            elseif(!$param->isOptional()) throw new Exception(-32602);
        }
        return $new;
    }
}